package se.devoteam.nexus.ldap;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.concurrent.locks.ReentrantLock;

import javax.naming.directory.SearchControls;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.devoteam.nexus.ldap.model.Configuration;
import se.devoteam.nexus.ldap.model.UserMappingConfiguration;
import se.devoteam.nexus.ldap.model.io.xpp3.LdapRealmConfigurationXpp3Reader;

/**
 * @author Anders Hammar
 */
@Component(role = LdapConfigurationManager.class)
public class DefaultLdapConfigurationManager implements LdapConfigurationManager {

	private final Logger logger = LoggerFactory.getLogger(DefaultLdapConfigurationManager.class);

    @org.codehaus.plexus.component.annotations.Configuration(value = "${ldap-xml-file}")
	private File ldapConfiguration;

	private Configuration configuration = null;
	private String searchUserFilterExpr = null;
	private String searchUsersFilterExpr = null;
	private String listUsersFilterExpr = null;

	private ReentrantLock lock = new ReentrantLock();

	protected Logger getLogger() {
		return logger;
	}

	public String getProviderUrl() {
		return getConfiguration().getProviderUrl();
	}

	public String getSecurityCredentials() {
		return getConfiguration().getSecurityCredentials();
	}

	public String getSecurityPrincipal() {
		return getConfiguration().getSecurityPrincipal();
	}

	public String getSecurityProtocol() {
		return getConfiguration().getSecurityProtocol();
	}

	public String getSecurityAuthentication() {
		return getConfiguration().getSecurityAuthentication();
	}

	public String getInitialContextFactory() {
		String ctxFactoryClassName = getConfiguration().getInitialContextFactory();
		if (ctxFactoryClassName == null) {
			ctxFactoryClassName = Configuration.DEFAULT_CONTEXT_FACTORY_CLASS;
		}
		return ctxFactoryClassName;
	}

	public String getReferral() {
		return getConfiguration().getReferral();
	}
	
    public boolean getIgnorePartialResultException() {
    	return getConfiguration().isIgnorePartialResultException();
    }
	
	public String getUserIdAttribute() {
    	String userIdAttribute = getConfiguration().getUserMapping().getUserIdAttribute();
    	if (userIdAttribute == null) {
    		userIdAttribute = UserMappingConfiguration.DEFAULT_USERID_ATTRIBUTE;
    	}
    	return userIdAttribute;
    }

    public String getNameAttribute() {
    	String nameAttribute = getConfiguration().getUserMapping().getNameAttribute();
    	if (nameAttribute == null) {
    		nameAttribute = UserMappingConfiguration.DEFAULT_NAME_ATTRIBUTE;
    	}
    	return nameAttribute;
    }

    public String getEmailAddressAttribute() {
    	String emailAttribute = getConfiguration().getUserMapping().getEmailAddressAttribute();
    	if (emailAttribute == null) {
    		emailAttribute = UserMappingConfiguration.DEFAULT_EMAILADDRESS_ATTRIBUTE;
    	}
    	return emailAttribute;
    }
    
    public String getUserBaseDn() {
    	return getConfiguration().getUserMapping().getBaseDn();
    }
    
    public int getUserSearchScope() {
    	boolean searchSubtree = getConfiguration().getUserMapping().isSearchSubtree();
    	int result;
		if (searchSubtree) {
			result = SearchControls.SUBTREE_SCOPE;
		} else {
			result = SearchControls.ONELEVEL_SCOPE;
		}
    	
    	return result;
    }
    
    public String getUserObjectClass() {
    	String userObjectClass = getConfiguration().getUserMapping().getObjectClass();
    	if (userObjectClass == null) {
    		userObjectClass = UserMappingConfiguration.DEFAULT_OBJECT_CLASS;
    	}
    	return userObjectClass;
    }

    private String getSearchFilterExpression() {
    	String searchFilterExpr = getConfiguration().getUserMapping().getSearchFilterExpression();
    	if (searchFilterExpr == null) { // not configured, construct default expression
			getLogger().debug("Constructing default search filter expression");
    		StringBuilder tmp = new StringBuilder();
    		tmp.append("(&(objectClass=");
    		tmp.append(getUserObjectClass());
    		tmp.append(")(");
    		tmp.append(getUserIdAttribute());
    		tmp.append("={0}))");
    		searchFilterExpr = tmp.toString();
    	} else { // expression configured
    		// check that the configured expression includes '{0}'
    		if (!searchFilterExpr.contains("{0}")) {
    			getLogger().warn("Configured search filter expression does not include '{0}'");
    		}
    	}
    	
    	return searchFilterExpr;
    }
    
    public String getSearchUserFilterExpr() {
    	String expr = this.searchUserFilterExpr;
		if (expr != null) {
			return expr;
		}

		this.lock.lock();
		getLogger().debug("Creating search user filter expression");
		expr = getSearchFilterExpression();
		this.searchUserFilterExpr = expr;
		this.lock.unlock();

		return expr;
	}

	public String getSearchUsersFilterExpr() {
    	String expr = this.searchUsersFilterExpr;
		if (expr != null) {
			return expr;
		}

		this.lock.lock();
		getLogger().debug("Creating search users filter expression");
		String tmp = getSearchFilterExpression();
		expr = tmp.replace("{0}", "{0}*");
		this.searchUsersFilterExpr = expr;
		this.lock.unlock();

		return expr;
	}

	public String getListUsersFilterExpr() {
    	String expr = this.listUsersFilterExpr;
		if (expr != null) {
			return expr;
		}

		this.lock.lock();
		getLogger().debug("Creating list users filter expression");
		String tmp = getSearchFilterExpression();
		expr = tmp.replace("{0}", "*");
		this.listUsersFilterExpr = expr;
		this.lock.unlock();

		return expr;
	}

	/**
	 * Clears any cached configuration information. Any subsequent call to any of the configuration
	 * methods will reload the configuration file.
	 */
	public void clearCache() {
		this.lock.lock(); // make sure we aren't fiddling with save/load process

		this.configuration = null;
		this.searchUserFilterExpr = null;
		this.searchUsersFilterExpr = null;
		this.listUsersFilterExpr = null;

		this.lock.unlock();
	}

	private Configuration getConfiguration() {
		Configuration config = this.configuration;
		if (config != null) {
			return config;
		}

		this.lock.lock();

		Reader fr = null;
		try {
			getLogger().debug("Reading configuration file");
			FileInputStream is = new FileInputStream(ldapConfiguration);
			LdapRealmConfigurationXpp3Reader reader = new LdapRealmConfigurationXpp3Reader();
			fr = new InputStreamReader(is);

			config = reader.read(fr);
			this.configuration = config;
		} catch (FileNotFoundException e) {
			getLogger().error("Configuration file not found; please verify configuration of the 'ldap-xml-file' property", e);
			throw new RuntimeException("LDAP realm configuration file not found", e);
		} catch (IOException e) {
			getLogger().error("IOException while retrieving configuration file", e);
			throw new RuntimeException("Cannot read LDAP realm configuration file", e);
		} catch (XmlPullParserException e) {
			getLogger().error("Invalid XML configuration", e);
			throw new RuntimeException("Cannot read LDAP realm configuration file", e);
		} finally {
			if (fr != null) {
				try {
					fr.close();
				} catch (IOException e) {
					// just closing if open
				}
			}

			this.lock.unlock();
		}

		return config;
	}
}
